Decouple ToolTips/ChartZoom/Cursor from PlotContent rendering pipeline#942
Merged
timmolter merged 9 commits intoMay 22, 2026
Merged
Conversation
Remove ToolTips, ChartZoom, and Cursor from PlotContent_* pipeline. PlotContent_* now populates PlotInteractionData during doPaint(). XChartPanel reads interactionData after chart.paint() to drive all interaction painting. - Add PlotInteractionData struct with ToolTipData and CursorData - PlotContent_.java: replace toolTips/chartZoom fields with interactionData - PlotContent_XY/Category_Bar/HorizontalBar/Bubble/OHLC/Box/HeatMap/Pie/Dial/Radar: replace toolTips.addData() -> interactionData.addToolTip(), cursor refs removed - Chart.java: add enableInteractionData() and getInteractionData() - ToolTips.java: remove self-injection, add plotBounds field and setData() - ChartZoom.java: remove self-injection - Cursor.java: remove self-injection, add plotBounds and setData(), remove addData/clearDataPoints - XChartPanel.java: add cursor/chartZoom fields, call enableInteractionData(), update componentResized, rewrite paintComponent() for two-phase painting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
isToolTipsEnabled, isToolTipsAlwaysVisible, ToolTipType, isZoomEnabled, zoomSelectionColor, zoomResetByDoubleClick, zoomResetByButton, and isCursorEnabled are Swing-panel behaviors, not chart visual styles. - Remove from Styler / AxesChartStyler / XYStyler / OHLCStyler - Remove from Theme interface and AbstractBaseTheme - Add fields + fluent setters to XChartPanel - Extract ToolTipType as top-level enum in org.knowm.xchart - Simplify PlotContent_* guards to interactionData != null - Refactor XChartPanel constructor wiring into rewireInteractions() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move setToolTipsEnabled, setToolTipsAlwaysVisible, setToolTipType, setZoomEnabled, setZoomResetByDoubleClick, setZoomResetByButton, and setCursorEnabled calls from chart Styler to XChartPanel in all 37 affected demo files. Also fix SwingWrapper.displayChartMatrix() to use invokeAndWait (was invokeLater) so getXChartPanel(i) is safe to call immediately after display. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the generic placeholder chart with the exact chart from the original bug report: logarithmic Y-axis, x in [-3, 3], y = 10^x, LegendPosition.InsideNW, XAxisLabelRotation 45, red tooltip border. The Javadoc is also updated to describe the real fix — the two-phase PlotInteractionData architecture — rather than the interim null-guard approach documented previously. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
toolTipsAlwaysVisible and toolTipType are rendering/visual properties that control data-label output — they belong on the Styler, not on XChartPanel. Moving them ensures BitmapEncoder output includes the rendered labels when isToolTipsAlwaysVisible() is true, restoring the behavior present before the interaction-decoupling refactor. Changes: - Styler: add toolTipsAlwaysVisible (default false) and toolTipType (default xAndYLabels) fields with getters/setters - Chart: add paintAlwaysVisibleToolTips(Graphics2D) protected helper that creates a ToolTips in always-visible mode and renders all data-point labels - All 10 chart paint() methods: call enableInteractionData() when isToolTipsAlwaysVisible(), then call paintAlwaysVisibleToolTips() at end - XChartPanel: remove toolTipsAlwaysVisible field/setter; remove toolTipType field/setter; hover ToolTips always created with alwaysVisible=false and toolTipType read from chart.getStyler() - TestForIssue862: setToolTipsAlwaysVisible/setToolTipType moved to getChart() on the styler so the saved PNG now contains rendered tooltip labels - BarChart07, TestForIssue106, TestForIssue370, TestForIssue545: migrate setToolTipsAlwaysVisible/setToolTipType calls from XChartPanel to styler Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Tool Tips: distinguish hover (XChartPanel.setToolTipsEnabled) from always-visible labels (styler.setToolTipsAlwaysVisible); note that always-visible labels appear in BitmapEncoder/headless output too; fix Styler.ToolTipType -> ToolTipType top-level enum reference - Zoom: move example from chart.getStyler() to XChartPanel setters - Cursor: move setCursorEnabled to XChartPanel; keep visual properties on styler - BoxChart example: move setToolTipsEnabled to XChartPanel Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Each demo chart that configures interaction features (toolTips, zoom, cursor) in its main() method now also overrides customizePanel() so those features work when the chart is displayed inside XChartDemo. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Member
Author
When componentResized fires (e.g. after setBottomComponent in JSplitPane), rewireInteractions() creates a new ToolTips instance with an empty toolTipList. If paintComponent() ran before that event, the new ToolTips never gets setData() called on it. The user's mouse moves find an empty list, no repaint is triggered, and tooltips never appear. Fix: call repaint() after wiring interactions so paintComponent() always runs (and calls setData()) after any rewiring. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR refactors XChart’s Swing interaction features (ToolTips, ChartZoom, Cursor) to be decoupled from the core PlotContent_* rendering pipeline, preventing headless rendering (e.g., BitmapEncoder) from depending on Swing-wired state and addressing the root cause of issue #862. It also moves interaction enablement/configuration from stylers to XChartPanel, while keeping “always-visible” data labels as a rendering concern.
Changes:
- Introduces
PlotInteractionDataas a two-phase “collect during chart render → consume in panel paint” mechanism. - Removes tooltip/zoom/cursor self-injection into plot content and rewires interactions to be owned by
XChartPanel. - Updates demos/docs to configure hover interactions via
XChartPanel, and adds support for always-visible labels in headless rendering.
Reviewed changes
Copilot reviewed 76 out of 76 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| xchart/src/test/java/org/knowm/xchart/BitmapEncoderTest.java | Updates regression tests for #862 (though current assertions no longer exercise tooltip/zoom-enabled scenarios). |
| xchart/src/main/java/org/knowm/xchart/XYChart.java | Enables interaction-data collection when always-visible labels are enabled; paints always-visible labels post-render. |
| xchart/src/main/java/org/knowm/xchart/XChartPanel.java | Owns ToolTips/Cursor/ChartZoom; rewires listeners and paints interactions using PlotInteractionData. |
| xchart/src/main/java/org/knowm/xchart/ToolTipType.java | Promotes tooltip label type to a top-level public enum. |
| xchart/src/main/java/org/knowm/xchart/SwingWrapper.java | Changes chart display creation to be synchronous (invokeAndWait) to allow immediate panel access. |
| xchart/src/main/java/org/knowm/xchart/style/XYStyler.java | Removes cursor/zoom enablement flags from styler; keeps cursor appearance settings. |
| xchart/src/main/java/org/knowm/xchart/style/theme/Theme.java | Removes interaction enablement defaults from Theme (tooltips/zoom/cursor). |
| xchart/src/main/java/org/knowm/xchart/style/theme/AbstractBaseTheme.java | Drops cursor-enabled override since enablement moved off Theme. |
| xchart/src/main/java/org/knowm/xchart/style/Styler.java | Removes hover-tooltip enablement; keeps always-visible labels as a rendering option and uses new ToolTipType. |
| xchart/src/main/java/org/knowm/xchart/style/OHLCStyler.java | Removes zoom enablement covariant overrides (zoom now panel-owned). |
| xchart/src/main/java/org/knowm/xchart/style/AxesChartStyler.java | Removes zoom enablement/configuration state from axes stylers. |
| xchart/src/main/java/org/knowm/xchart/RadarChart.java | Adds always-visible tooltip label rendering path via interaction-data enablement. |
| xchart/src/main/java/org/knowm/xchart/PieChart.java | Adds always-visible tooltip label rendering path via interaction-data enablement. |
| xchart/src/main/java/org/knowm/xchart/OHLCChart.java | Adds always-visible tooltip label rendering path via interaction-data enablement. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/ToolTips.java | Removes self-injection; consumes PlotInteractionData via setData() and uses plot bounds from the struct. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotInteractionData.java | New interaction DTO for tooltips + cursor points + plot bounds. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_XY.java | Switches tooltip/cursor collection to PlotInteractionData (no direct interaction painting). |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Radar.java | Switches tooltip collection to PlotInteractionData. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Pie.java | Switches tooltip collection to PlotInteractionData. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_OHLC.java | Switches tooltip collection to PlotInteractionData (including composite hit areas). |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_HorizontalBar.java | Switches tooltip collection to PlotInteractionData. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_HeatMap.java | Switches tooltip collection to PlotInteractionData. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Dial.java | Switches tooltip collection to PlotInteractionData. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Category_Bar.java | Switches tooltip collection to PlotInteractionData. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Bubble.java | Switches tooltip collection to PlotInteractionData. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_Box.java | Switches tooltip collection to PlotInteractionData. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/PlotContent_.java | Removes embedded ToolTips/ChartZoom plumbing; clears and populates PlotInteractionData bounds per paint. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/Cursor.java | Removes self-injection; consumes PlotInteractionData via setData() and uses plot bounds from the struct. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/ChartZoom.java | Removes self-injection; reads zoom config from XChartPanel. |
| xchart/src/main/java/org/knowm/xchart/internal/chartpart/Chart.java | Adds public enableInteractionData() / getInteractionData() and a shared paintAlwaysVisibleToolTips() helper. |
| xchart/src/main/java/org/knowm/xchart/HorizontalBarChart.java | Adds always-visible tooltip label rendering path via interaction-data enablement. |
| xchart/src/main/java/org/knowm/xchart/HeatMapChart.java | Adds always-visible tooltip label rendering path via interaction-data enablement. |
| xchart/src/main/java/org/knowm/xchart/DialChart.java | Adds always-visible tooltip label rendering path via interaction-data enablement. |
| xchart/src/main/java/org/knowm/xchart/CategoryChart.java | Adds always-visible tooltip label rendering path via interaction-data enablement. |
| xchart/src/main/java/org/knowm/xchart/BubbleChart.java | Adds always-visible tooltip label rendering path via interaction-data enablement. |
| xchart/src/main/java/org/knowm/xchart/BoxChart.java | Adds always-visible tooltip label rendering path via interaction-data enablement. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue862.java | Updates standalone repro to demonstrate headless rendering safety + panel-owned hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue770.java | Moves zoom enablement to panel after display. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue593.java | Moves cursor enablement to panel after display. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue545.java | Moves hover tooltip enablement to panel after display; keeps always-visible labels on styler. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue54_2.java | Moves hover tooltip enablement to panel after display. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue54_1.java | Moves hover tooltip enablement to panels created by matrix wrapper. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue530.java | Moves hover tooltip enablement to panel after display. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue410.java | Moves hover tooltip enablement to panel after display. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue370.java | Moves hover tooltip enablement to panels created by matrix wrapper; keeps always-visible labels on styler. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue349.java | Moves zoom enablement to panel. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue335.java | Moves hover tooltip enablement to panel after display. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue291.java | Moves hover tooltip enablement to panel after display. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue244.java | Moves hover tooltip enablement to panels created by matrix wrapper. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue210.java | Moves hover tooltip enablement to panels created by matrix wrapper. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue189_1.java | Moves hover tooltip enablement to a specific panel in matrix wrapper. |
| xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue106.java | Updates tooltip type enum usage and moves hover enablement to panels created by matrix wrapper. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/XChartDemo.java | Adds ExampleChart.customizePanel() hook to configure panel interactions when switching examples. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/theme/ThemeChart03.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/radar/RadarChart02.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/radar/RadarChart01.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/pie/PieChart02.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/ohlc/OHLCChart03.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/ohlc/OHLCChart02.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/ohlc/OHLCChart01.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart09.java | Implements customizePanel() to enable cursor. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/line/LineChart05.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/horizontalbar/HorizontalBarChart04.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart05.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart04.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/heatmap/HeatMapChart03.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/ExampleChart.java | Adds customizePanel() extension point for panel-owned interactions. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/dial/DialChart02.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/dial/DialChart01.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart09.java | Implements customizePanel() to enable cursor. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/date/DateChart01.java | Implements customizePanel() to enable zoom. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bubble/BubbleChart01.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/box/BoxChart03.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/box/BoxChart02.java | Implements customizePanel() to enable hover tooltips. |
| xchart-demo/src/main/java/org/knowm/xchart/demo/charts/bar/BarChart07.java | Updates tooltip type enum usage and implements customizePanel() to enable hover tooltips. |
| README.md | Updates public docs for panel-owned hover interactions and styler-owned always-visible labels/appearance. |
- PlotInteractionData: remove public modifier (now package-private) - Chart.getInteractionData(): remove public modifier (now package-private) - Chart: add public consumeInteractionData(Graphics2D, ToolTips, Cursor) bridge so XChartPanel never imports PlotInteractionData - XChartPanel: remove unused ToolTipType and PlotInteractionData imports; use consumeInteractionData(); only enable interaction data when tooltips or cursor are active (not for zoom-only) - SwingWrapper: add EDT guard (isEventDispatchThread check) for both displayChart() and displayChartMatrix(); restore interrupt flag on InterruptedException - BitmapEncoderTest: rename/update tests to actually exercise the #862 regression (setToolTipsAlwaysVisible) and clarify headless no-NPE test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Motivation
The Swing-interactive features (
ToolTips,ChartZoom,Cursor) were tightly coupled into the corePlotContent_*rendering pipeline in two problematic ways:Self-injection anti-pattern:
ToolTips,ChartZoom, andCursorconstructors wrote themselves directly intochart.plot.plotContentvia package-private field access. This caused NPEs whenBitmapEncoder.saveBitmap()was called while these objects were still wired in (issue NPE on tooltips with BitmapEncoder.saveBitmap #862 root cause).Styler pollution: Feature flags like
isToolTipsEnabled,isZoomEnabled, andisCursorEnabledlived onStyler/AxesChartStyler/XYStyler-- chart visual style classes -- even though they control Swing panel behavior that is irrelevant during headless rendering.Approach
Commit 1: Option C two-phase data collection (
PlotInteractionData)Introduces a plain data-transfer struct
PlotInteractionDatathatPlotContent_*populates during rendering, replacing direct calls totoolTips.addData()andcursor.addData().XChartPanelreads the struct afterchart.paint()and drives all interaction painting.PlotInteractionData.java: holdsToolTipDatalist,CursorDatalist, andplotBoundsChart.java: addsenableInteractionData()/getInteractionData()public methodsPlotContent_*subclasses: replacetoolTips.addData()/cursor.addData()calls withinteractionData.addToolTip()/interactionData.addCursorPoint()PlotContent_.java: removestoolTips/chartZoomfields and the three nullable-field settersToolTips/Cursor: remove self-injection from constructors; addsetData(PlotInteractionData)to consume the struct and replace allchart.plot.plotContent.getBounds()callsChartZoom: remove self-injection (one line)XChartPanel:cursorandchartZoombecome fields; callsenableInteractionData()in constructor;paintComponent()does two-phase render -> read -> paint interactionsCommit 2: Move interaction feature flags off Styler to XChartPanel
isToolTipsEnabled,isToolTipsAlwaysVisible,ToolTipType,isZoomEnabled,zoomSelectionColor,zoomResetByDoubleClick,zoomResetByButton, andisCursorEnabledare panel behaviors, not chart styles.Styler,AxesChartStyler,XYStyler,OHLCStyler,Theme, andAbstractBaseThemeToolTipTypepromoted to a top-level public enum (org.knowm.xchart.ToolTipType) instead of aStylerinner typeXChartPanelgains 8 fields with fluent setters and a privaterewireInteractions()methodPlotContent_*tooltip guards simplify frominteractionData != null && styler.isToolTipsEnabled()to justinteractionData != nullChartZoomreads zoom config fromxChartPanelgetters instead ofchart.getStyler()New user API (breaking change)
Before:
After:
Testing
All 44 existing tests pass. The
BitmapEncoderTestwas updated to reflect that interaction setup now belongs on the panel, not the chart styler.